Arduino+Esp8266实现远程的模块控制 您所在的位置:网站首页 Esp8266 远程打印 Arduino+Esp8266实现远程的模块控制

Arduino+Esp8266实现远程的模块控制

2024-07-12 10:26| 来源: 网络整理| 查看: 265

最近尝试使用Arduino配合esp8266实现远程控制Arduino上外接的某些模块,于是开始一边摸索一遍折腾开始了一周的尝试,期间得到很一些对自己很有用的经验,遂记录。本次使用的策略是Arduino通过串口与esp8266通信,esp8266通过wifi连接到网络,然后通过mqtt协议订阅模块的操作信息,客户端就可以通过远程向mqtt发送相关主题的操作信息从而控制Arduino做出相应的约定动作。

使用硬件信息 国版Ardunio Uno Rev3【MacOS搭建ardunio环境】 Esp8266-01s 动作模块使用舵机(或者灯泡等) 关于ESP8266

ESP8266存在3种工作模式:STA,AP,STA+APSta模式: Station,类似于无线终端,本身不接受无线接入,可连接到AP,也就是可以连接wifi。一般无线网卡即工作在该模式AP模式:相当于路由器,自己发射WiFi,终端可以连接上它,但是无法像sta模式那样连接其他WiFi。STA+AP模式:既可以自己发射WiFi供其他终端连接,又可以做终端连接其他WiFi。这也是默认的出厂模式。

ESP8266默认携带AT固件,支持AT指令,通过AT指令可以实现模式切换,修改波特率,Wifi连接,TCP连接等功能。当然,使用不同的固件实现的功能也将不同。例如这里的支持mqtt的AT固件

Arduino与8266的串口通信

ESP8266与Arduino的接线,软硬串口的通信可以参见这篇文章。

ESP8266AT指令不可用

Arduino接上8266之后,通过串口发送AT指令没有响应。于是尝试重刷esp8266的支持AT指令固件

ESP8266重刷固件

在乐鑫官网获取下载工具【需要在windows下使用】同样在乐鑫官网获取所需的AT固件下载工具和固件使用方式可以参考这篇文章。烧录使用的连接模块,我使用的是学弟借的ESP专用的U转串模块,插上即用(下图)。也有人使用pl2303。

重刷了固件之后AT指令还是没有生效。排除了接线问题,看到这篇文章提示,有可能是供电不足的原因。重新紧了一下线,重插电源线(3.3V),8266的蓝灯一闪,然后微弱发光,这时候AT指令就生效了。(我使用的是MBP笔记本雷电口+usb转接器)b站有个大佬提供了一个使用外部电源输入的实现方式。所以AT指令不生效的情况,可以考虑一下供电的问题

ESP8266连接wifi与MQTT服务器

紧接着是8266上的代码编写,需要实现

Wifi连接 MQTT服务通信 通过串口发送消息到Arduino ESP8266代码烧录ESP8266代码编写

ESP8266的代码可以使用ArduinoIDE编写,同时支持8266板的库函数管理相关。也可以使用VSCode安装PlatformIO扩展,然后在PlatformIO里安装相关应用库。使用ArduinoIDE,需要先安装esp8266的开发板支持。否则使用如ESP8266WiFi.h这样的库函数会报错sketch_apr17a:15:10: error: ESP8266WiFi.h: No such file or directory

打开Arduino的设置页,在附加开发版管理网址中填入http://arduino.esp8266.com/stable/package_esp8266com_index.json 打开工具>开发板>开发板管理器搜索esp8266,点击安装 安装成功以后即可在开发板选项中看到ESP8266 Board,选择ESP8266 Module,就可以使用8266的库函数进行开发。注意esp8266代码编译和上传(烧录)时,需要确认开发板和端口的选择,否则可能会失败

8266开发板支持包下载失败曾经在学校安装支持包速度蹭蹭,在家却是龟速+反复失败。于是想到了手动下载安装:

浏览器打开http://arduino.esp8266.com/stable/package_esp8266com_index.json,可以看到其实是一大段记录支持包所需要的依赖包的地址(有点类似npm的package.json)最主要的esp8266-2.7.4.zip,通过github下载其实速度还是感人。后来使用迅雷,10秒结束战斗!!真是个防止浪费生命的神器。需要放到/Library/Arduino15/staging/packages目录下,再点击安装,就会跳过这个包了还有一些零碎的依赖,看起来主要是根据不同的编译环境的一些不同包,上边的博客中提到可以把所有带osx,apple的都下下来。经过实际测试,应该不需要,主要是以下几个包。(测试使用笨方法,即点击安装,下载过程会在上述文件中生成文件,然后经过文件名到上边依赖包目录找到对应文件及地址进行下载,所以可能有一些比较小下载极快的这里就不列出来)x86_64-apple-darwin14.xtensa-lx106-elf-b40a506.1563313032.tar.gzx86_64-apple-darwin14.mkspiffs-7fefeac.1563313032.tar.gzx86_64-apple-darwin14.mklittlefs-fe5bb56.1578453304.tar.gzpython3-macosx-portable.tar.gz

pyserial or esptool directories not found next to this upload.py tool编译代码时出现以上报错。翻阅一堆博客论坛,找到了解决方法,并且有可能是MacOSBigSur的问题。

下载 https://github.com/espressif/esptool/archive/v3.0.zip下载 https://github.com/pyserial/pyserial/archive/v3.4.zip下载解压出文件夹esptool/和pyserial/放到~/Library/Arduino15/packages/esp8266/hardware/esp8266/2.7.4/tools/文件夹下,替换原有的文件,具体点进目录中,根据文件名应该就明白了参考链接 Esp8266代码烧录

esp8266通过Arduino板子连接PC进行代码烧录,始终出现问题。说始终是因为前几个月就因为这个问题导致有个小项目搁置,所以出现这个问题也算是意料之中吧。主要现象是代码编译通过以后点击上传,始终出现Connecting...打点,过会就出现Failed to connect to ESP32: Timed out waiting for packet header的报错,上传失败。使用的接线是

将UTXD接到串口模块的TX上,CH_PD和VCC接3.3V,GND和GPIO0接GND这是烧录模式,如果要工作的话请将GPIO0脚悬空,即断开,否则设备不会正常工作!

找到一篇帖子,其中说解决方式是在8266EN和GND之间外接一个10uF的电容。感觉…不太实际,没有尝试。

后来看这篇博客时,发现它实际上也有说到这个问题。提到需要先断开8266的EN和IO0,然后在Connecting..打点时,IO0接地,EN接3.3V,程序可以继续烧录,否则会出现以上报错。帖子中提到该方式的成功率不高,确实我一直没有成功,它说还是使用U转串(下载器)进行烧录比较好。(我…怎么没有想到)

帖子中提到的,需要安装驱动,但是我没有,直接插上,点击上传就烧录了==,很成功,就像重刷固件一样,connecting之后log会显示烧写的地址位。同样,需要注意切换端口。

实际上我看了这篇博客才意识到了(是的 才),实际上烧录程序和输入固件是一样的,固件实际上也是包装好的程序,比如AT固件实际上只是就只通过接受串口的固定格式的数据(AT指令),然后操作并返回结果信息的程序罢了,而所谓的需要注意默认的波特率,是因为程序中Serial.begin同样的使用了该波特率罢了。

走通了代码的编写和烧录流程,接下来终于可以愉快的写代码了。

ESP8266代码编写ESP8266串口通信

在8266上发送串口数据和Arduino其实一样12Serial.begin(); // 指定串口波特率,注意通信的另一端(Arduino)需要统一波特率Serial.println(); // 输出数据。此外8266上的Serial对象还支持printf格式化输出方式

ESP8266连接Wifi

使用8266开发板自带库函数头文件ESP8266Wifi.h,很方便实现Wifi连接。基本代码如下12WiFi.begin(char* ssid, char* password); // 传入Wifi名和密码,开始连接if (Wifi.status() != WL_CONNECTED) // 确认连接状态具体代码示例:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172#include const char* ssid = "ESP8266 需要连接的WIFI的SSID";const char* password = "Wifi密码";void setup() { Serial.begin(9600); delay(10); Serial.print("Connecting to "); /* 明确将ESP8266设置为WiFi客户端,否则默认情况下,它将尝试同时充当客户端和接入点,并可能导致WiFi网络上的其他WiFi设备出现网络问题 */ WiFi.mode(WIFI_STA); // 开始连接 WiFi.begin(ssid, password); // 状态确认 while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // 连接成功 Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP());}int whetherConnect = 0;WiFiClient client;const uint16_t port = 8082;const char * host = "你的IP"; // ip or dnsvoid loop() { // 确认socket连接状态 if (client.connected() == true) whetherConnect = 1; else whetherConnect = 0; if (whetherConnect == 0) { Serial.print("connecting to " + host); if (!client.connect(host, port)) { Serial.println("connection failed \n wait 5 sec..."); return; } else { whetherConnect = 1; } } if (Serial.available()) { // 读取硬串口 if (Serial.read() == '#') { // 获取ssid列表 int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) { client.println("no networks found"); } else { client.println(n + " networks found"); for (int i = 0; i < n; ++i) { // Print SSID and RSSI for each network found client.print(WiFi.SSID(i)); client.println(WiFi.RSSI(i)); } } // This will send the request to the server client.println("------------------------------------------"); //read back one line from server Serial.println(client.readStringUntil('\r')); Serial.println("closing connection"); client.stop(); } }}

ESP8266与MQTT协议

库管理中导入PubSubClient.h头文件,实现MQTT服务端的连接以及消息的订阅和发布。代码也不复杂123456789101112WiFiClient espClient;PubSubClient client(espClient);client.setServer(char* server_ip, int port); // 设置服务器IP和端口(思考,如果IP下还带有子目录该如何实现?)client.setCallback(callback); // 设置回调,当有消息传入时会执行该回调client.connected(); // 判断连接状态client.connect(String clientId); // 开始连接,传入客户端IDclient.publish(char* topic, char* msg); // 消息发布client.subscribe(char* topic); // 消息订阅client.loop(); // 处理保持活动信号,以及处理传入消息void callback(char *topic, byte * payload, unsigned int length) { } // 主题,消息,消息长度具体代码示例:参考1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950#include#includeconst char* ssid = "ESP8266 需要连接的WIFI的SSID";const char* password = "Wifi密码";const char* mqtt_server = "MQTT服务器IP";const int mqtt_port = MQTT服务端口;WiFiClient espClient;PubSubClient client(espClient);void setup() { Serial.begin(9600); Serial.println("Connecting to "); connectWiFi(); // wifi连接如上文 client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); while (!client.connected()) { String client_id = String(WiFi.macAddress()); Serial.println("Connecting to public emqx mqtt broker....."); if (client.connect(client_id.c_str())) { Serial.println("Public emqx mqtt broker connected"); } else { Serial.print("failed with state "); Serial.print(client.state()); delay(2000); } } Serial.println("start to subsc"); char* topic = "hello"; client.publish(topic, "hello emqx"); client.subscribe(topic);}void callback(char *topic, byte * payload, unsigned int length) { Serial.print("Message arrived in topic: "); Serial.println(topic); Serial.printf("Message:(%d) ", length); for (int i = 0; i < length; i++) { Serial.print((char) payload[i]); } Serial.println(); Serial.println("-----------------------");}void loop() { client.loop();}还有大佬对PubSubClient库进行进一步封装实现了一个库。可以看看这个以及大佬编写的,关于客户端状态返回码的注释。如果连接MQTT服务器失败返回了状态码可以参考一下

int - 客户端状态,可以采用以下值 (常量定义在 PubSubClient.h):-4 : MQTT_ CONNECTION_ TIMEOUT - 服务器在保持活动时间内没有响应。-3 : MQTT_ CONNECTION_ LOST - 网络连接中断。-2 : MQTT_ CONNECT_ FAILED - 网络连接失败。-1 : MQTT_ DISCONNECTED - 客户端干净地断开连接。0 : MQTT_ CONNECTED - 客户端已连接。1 : MQTT_ CONNECT_ BAD_ PROTOCOL - 服务器不支持请求的MQTT版本。2 : MQTT_ CONNECT_ BAD_ CLIENT_ ID - 服务器拒绝了客户端标识符。3 : MQTT_ CONNECT_ UNAVAILABLE - 服务器无法接受连接。4 : MQTT_ CONNECT_ BAD_ CREDENTIALS - 用户名/密码被拒绝。5 : MQTT_ CONNECT_ UNAUTHORIZED - 客户端无权连接。

关于MQTT

对于MQTT,此前一直不了解,这几天查了些资料,根据自己的理解对MQTT进行了简单的介绍,大概是对TCP协议进一步封装的一个应用层协议,主要是消息订阅与发布广播的机制。可以看看我对于MQTT的认识

查阅过程中,看到Arduino社区一位大佬写下的科普贴,觉得对理解MQTT还是很有帮助。以及如果有node.js的环境,也可以使用node.js搭建MQTT服务器和客户端,实现消息的发送订阅。使用mosca库3 5行就可以实现。

Arduino接收信息并输出Arduino接收串口信息

实际上Arduino通过软串口连接8266的示例在上边以及提供了,但我还是提供一下我的实现方式。

Arduino处理数据并控制动作模块Arduino+舵机

舵机图如下,能通过pin口接收一个角度值然后将旋桨旋转到该角度。舵机细节讲解

接线方式参考

控制代码如下

1234#include // 头文件Servo myServo;pinMode(int pin, myServo); // 和控制led灯类似,需传入对应的pin口号myServo.write(int angle); // 写入角度值,进行旋转 Arduino处理串口数据

说实话通过串口接受的数据处理也是废了不少脑筋。经过测试,通过SoftwareSerial.read()读取的数据实际上是byte的字节流,需要强转为char类型,组装为原文字符串;而Serial.print()方法打印的数据,稍有不慎会出现乱序和重复的问题。然而这些问题都还没有结论,我也暂时没有深究。

由于在8266中输出的字符串文本接受时都是零散的字符,因此我输出文本时,将每一段文本都加上#开头,在Arduino接收时以一个#以及一个结尾的\n作为一句文本的标志,以此获得完整的字符串。然而如果是要接受一段mqtt协议相关的消息,那么一段消息我需要得到的最主要信息就是topic和message至少两个部分。因此我决定让8266输出的信息拼装为一个json串,然后Arduino上以json的方式取用数据。

最后实现代码

实际上这个地方大可不必如此麻烦,直接让8266发送角度值,Arduino接受后送给舵机发动就好了,没必要整花里胡哨的。但我是考虑了如果Arduino上如果连接了多个外设,那就需要区分topic和消息内容了,索性折腾了一下

Arduino上的Json库:ArduinoJson同样的库管理中下载该库。由于网上大部分都是v5版本,为此还在其官网徜徉了一番,找到版本变更点,以及v6正确打开方式。十分详尽,此处不过多介绍使用了

代码示例实现esp8266wifi连接且通过软串口输出到ardunio12345678910111213141516171819202122232425262728293031323334353637383940414243// ESP8266代码#includeconst char* ssid = "ssid";const char* pwd = "password";void setup() { Serial.begin(9600); Serial.println("Connecting to "); WiFi.begin(ssid, pwd); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Wifi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP());}// Ardunio代码#include SoftwareSerial mySerial(10, 11); // TX,RXvoid setup() { Serial.begin(9600); while (!Serial) {} Serial.println("Goodnight moon!"); delay(300); mySerial.begin(9600);}void loop() { if (mySerial.available()) { Serial.write(mySerial.read()); } if (Serial.available()) { mySerial.write(Serial.read()); }}

值得注意的是,一直以来很多论坛说的Ardunio和ESP8266的RX和TX需要反着接,但是尝试之后好像不是这样的。并且成功的最后一步操作是,把两条反接的线再反过来一次,即TX对TX,RX对RX。这里不是很明白,望交流告知。因此如果没有反应,可以尝试把RX和TX反过来试试。

舵机123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150// ESP8266端代码#include#includeconst char* ssid = "ssid";const char* pwd = "pwd";const char* mqtt_server = "192.168.1.103";const int mqtt_port = 1883;WiFiClient espClient;PubSubClient client(espClient);void connectWifi() { WiFi.begin(ssid, pwd); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Wifi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP());}void reConnectMqtt() { while (!client.connected()) { String client_id = "esp8266-client-"; client_id += String(WiFi.macAddress()); Serial.println("Connecting to public emqx mqtt broker....."); if (client.connect(client_id.c_str())) { Serial.println("Public emqx mqtt broker connected"); } else { Serial.print("failed with state "); Serial.print(client.state()); delay(2000); } } Serial.println("start to subsc"); char* topic = "hello"; client.publish(topic, "hello emqx"); client.subscribe(topic);}void setup() { Serial.begin(9600); Serial.println("Connecting to "); connectWifi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); reConnectMqtt();}void callback(char *topic, byte * payload, unsigned int length) { String content = "#{\"topic\":\"" + String(topic) + "\", \"msg\":\""; for (int i = 0; i < length; i++) { content += (char) payload[i]; } content += "\", \"other\":213}"; Serial.println(content);}void loop() { if (!client.connected()) { Serial.println("Reconnenct"); reConnectMqtt(); } client.loop();}// ============================// Arduino端代码#include #include #include Servo myServo;int ServoPin = 8;SoftwareSerial mySerial(10, 11);void setup() { Serial.begin(9600); pinMode(ServoPin, OUTPUT); myServo.attach(ServoPin); while (!Serial) {} Serial.println("Goodnight moon!"); delay(300); mySerial.begin(9600);}String con = "";/* * 0:未记录 * 1:记录日志 */int note = 0;void handler(String);void loop() { if (mySerial.available()) { char a = mySerial.read(); if (a == '\n' && note == 1) { // 记录中且读到/n:关闭记录且输出,并清空缓存; note = 0; handler(con); con = ""; return ; } else if (a == '#' && note == 1) { // 记录中又读到一个#:输出当前,清空缓存 handler(con); con = ""; return ; } if (a == '#') { note = 1; } else if (note == 1) { con += a; } }}StaticJsonDocument doc;void handler(String str) { char* con = str.c_str(); auto error = deserializeJson(doc, con); Serial.println(str); if (error) { Serial.println("parseJson Fail"); return; } char* topic = doc["topic"]; char* msg = doc["msg"]; int other = doc["other"]; Serial.println("**" + String(topic) + "**"); Serial.println("**" + String(msg) + "**"); Serial.println("**" + String(other) + "**"); if (String(topic) == "hello") { int te = atoi(msg); // Serial.println("Servo Angle " + te); // 若是打开这句输出,串口输出就会乱掉,不解 myServo.write(te); }}

总结

本次折腾了快一周,算是很有收获,搞清楚了之前一直云里雾里的8266编程和他的AT指令以及串口通信的使用。

ESP8266和Arduino就像是两个的单片机,如果需要做AT指令不好完成的操作,当然需要另外编写代码烧录到8266中。而如果只是连接Wifi还是可以通过AT指令完成的,甚至进行MQTT通信,也可以使用对应的AT固件完成。

实际上在各种查阅时候,确实看到某大佬自己编写的支持8266进行mqtt通信的指令固件,参见他的站点。对于这种大佬也是只有崇拜啦

更新2021-05-08 舵机抖动

使用Servo.h库操作舵机,出现问题:舵机在接到指令转动之前,会有极小角度的颤抖,然后再进行偏转;甚至在未接收到指令时,偶现出现自发的小幅转动。经过一番查阅,发现这篇博客,解决了舵机抖动问题。由文章分析可知,官方提供的Servo.h代码中使用了定时器中断,由于串口通信也需要使用定时器,所以如果同时使用了Servo和串口通信功能,那么会出现舵机抖动的问题。博主给出的解决方式,自己实现舵机的驱动程序,实际上也十分简单,也是往舵机的数字接口发送一定频率的高低电平实现,代码如下:123456789101112void servopulse(int angle)//定义一个脉冲函数{ //发送50个脉冲 for (int i = 0; i < 50; i++) { int pulsewidth = (angle * 11) + 500; //将角度转化为500-2480的脉宽值 digitalWrite(servoPin, HIGH); //将舵机接口电平至高 delayMicroseconds(pulsewidth); //延时脉宽值的微秒数 digitalWrite(servoPin, LOW); //将舵机接口电平至低 delayMicroseconds(20000 - pulsewidth); } // delay(20);}



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有